class AssociationEditor {
    constructor(viewportEle, commandLineEle, dotNetHelper) {
        this.viewportEle = viewportEle;
        this.commandLineEle = commandLineEle;
        this.dotNetHelper = dotNetHelper;

        this.editorEle = viewportEle.querySelector('.entity-association-editor');
        this.jsPlumbInstance = window.jsPlumb.newInstance({ container: this.editorEle });
        this.navigator = window.createJsPlumbNavigator(this.jsPlumbInstance, {
            viewportUpdated: this.handleViewportUpdated.bind(this),
            selectionChanged: this.handleSelectionChanged.bind(this),
            nodesLayouted: this.handleNodesLayouted.bind(this)
        });

        this.jsPlumbInstance.addSourceSelector('.connection-source-endpoint', { endpoint: 'Blank' });
        this.jsPlumbInstance.addTargetSelector('.connection-target-endpoint', { endpoint: 'Blank' });

        this.jsPlumbInstance.importDefaults({
            endpoint: { type: 'Blank' },
            paintStyle: { stroke: '#95A5A6', strokeWidth: 1 }
        });

        this.jsPlumbInstance.bind('drag:stop', this.handleDragStop.bind(this));
        this.jsPlumbInstance.bind('beforeDrag', this.handleBeforeDrag.bind(this));
        this.jsPlumbInstance.bind('beforeDrop', this.handleBeforeDrop.bind(this));

        this.viewportEle.addEventListener('mouseup', this.handleEditorMouseUp.bind(this));
        document.body.addEventListener('mouseup', this.handleBodyMouseUp.bind(this));

        this.commandLineEle.addEventListener('keypress', this.handleCommandLineInput.bind(this));

        this.refresh();

        setTimeout(() => {
            this.navigator.bestFit()
        });
    }

    handleDragStop(e) {
        this.dotNetHelper.invokeMethodAsync('EntityMovedAsync', e.el.id, e.el.offsetLeft, e.el.offsetTop);
    }

    handleBeforeDrag(e) {
        const zoom = this.navigator.getZoom();
        const editorBounds = this.editorEle.getBoundingClientRect();

        this.sourceLocation = {
            x: editorBounds.left + e.endpoint.endpoint.computedValue[0] * zoom,
            y: editorBounds.top + e.endpoint.endpoint.computedValue[1] * zoom
        };

        const sourceEntity = this.findEntity(e.sourceId, this.sourceLocation);
        this.setConnectionDragFlag(sourceEntity);
    }

    handleBeforeDrop(e) {
        const sourceEntity = this.findEntity(e.sourceId, this.sourceLocation);
        const targetEntity = this.findEntity(e.targetId, this.targetLocation);
        if (sourceEntity.entityId != targetEntity.entityId || sourceEntity.propertyId != targetEntity.propertyId) {
            this.dotNetHelper.invokeMethodAsync('ConnectEntityAsync',
                sourceEntity.entityId, sourceEntity.propertyId,
                targetEntity.entityId, targetEntity.propertyId);
        }
    }

    handleEditorMouseUp(e) {
        this.targetLocation = { x: e.clientX, y: e.clientY };
    }

    handleBodyMouseUp() {
        this.clearConnectionDragFlag();
    }

    handleAssociationNameClick(e, connection, associationId) {
        const bounds = e.e.target.getBoundingClientRect();
        if (e.e.clientX > bounds.right) {
            this.jsPlumbInstance.deleteConnection(connection);
            this.dotNetHelper.invokeMethodAsync('DeleteAssociationAsync', associationId)
        } else {
            this.dotNetHelper.invokeMethodAsync('OpenAssociationAsync', associationId)
        }
    }

    handleViewportUpdated(e) {
        clearTimeout(this.viewportUpdateHandler);
        this.viewportUpdateHandler = setTimeout(() => {
            this.canvasVisualBounds = e;
            this.dotNetHelper.invokeMethodAsync('UpdateViewportBoundsAsync', e.left, e.top, e.width, e.height);
        }, 200);
    }

    handleSelectionChanged(elements) {
        this.dotNetHelper.invokeMethodAsync('SelectEntitiesAsync', elements.map(ele => ele.id));
    }

    handleCommandLineInput(e) {
        if ((e.code === 'Enter' || e.code === 'NumpadEnter') && e.srcElement.value) {
            this.dotNetHelper.invokeMethodAsync('ExecuteCommandAsync', e.srcElement.value).then(result => {
                if (result) {
                    e.srcElement.value = null;
                }
            });
        }
    }

    handleNodesLayouted(nodes) {
        this.dotNetHelper.invokeMethodAsync('NodesLayoutAsync', nodes);
    }

    setConnectionDragFlag(sourceEntity) {
        this.clearConnectionDragFlag();

        document.body.style.cursor = 'crosshair';

        this.editorEle.setAttribute('connection-dragging', true);

        if (sourceEntity.entityId) {
            this.sourceEntityEle = document.getElementById(sourceEntity.entityId);
            if (this.sourceEntityEle) {
                this.sourceEntityEle.setAttribute('connection-source-entity', true);
            }
        }

        if (sourceEntity.propertyId) {
            this.sourcePropertyEle = document.getElementById(sourceEntity.propertyId);
            if (this.sourcePropertyEle) {
                this.sourcePropertyEle.setAttribute('connection-source-property', true);
            }
        }
    }

    clearConnectionDragFlag() {
        document.body.style.cursor = 'default';

        this.editorEle.removeAttribute('connection-dragging');

        if (this.sourceEntityEle) {
            this.sourceEntityEle.removeAttribute('connection-source-entity');
            this.sourceEntityEle = null;
        }

        if (this.sourcePropertyEle) {
            this.sourcePropertyEle.removeAttribute('connection-source-property');
            this.sourcePropertyEle = null;
        }
    }

    findEntity(nodeId, point) {
        const entityNode = this.viewportEle.querySelector(`.entity-node[data-jtk-managed="${nodeId}"]`);
        const propertyNodes = entityNode.querySelectorAll('.property');
        for (const propertyNode of propertyNodes) {
            const bounds = propertyNode.getBoundingClientRect();
            if (bounds.x < point.x + 16 && bounds.y < point.y && bounds.right > point.x - 16 && bounds.bottom > point.y) {
                return { entityId: entityNode.id, propertyId: propertyNode.id };
            }
        }
        return { entityId: entityNode.id }
    }

    findNode(nodeId, entityNodes) {
        return entityNodes.values().find(x => x.id === nodeId);
    }

    connectNode(parent, child, association) {
        const associationId = association[0];
        const associationName = association[1];
        const parentSide = association[3];
        const childSide = association[5];
        if (parent !== child) {
            const connection = this.jsPlumbInstance.connect({
                source: child,
                target: parent,
                anchor: 'Continuous',
                connector: {
                    type: 'Straight'
                },
                hoverPaintStyle: {
                    stroke: '#C0392B',
                    strokeWidth: 2
                },
                overlays: [
                    {
                        type: "Custom",
                        options: {
                            create: () => {
                                const ele = document.createElement("DIV")
                                ele.className = 'association-child-side';
                                return ele
                            },
                            location: 0,
                        }
                    },
                    {
                        type: 'Label',
                        options: {
                            label: childSide,
                            cssClass: childSide === '1'
                                ? 'association-one-overlay'
                                : 'association-more-overlay',
                            location: '20px',
                        }
                    },
                    {
                        type: 'Label',
                        options: {
                            label: associationName,
                            cssClass: 'association-name-overlay',
                            location: 0.5,
                            events: {
                                click: e => this.handleAssociationNameClick(e, connection, associationId)
                            }
                        }
                    },
                    {
                        type: 'Label',
                        options: {
                            label: parentSide,
                            cssClass: parentSide === '1'
                                ? 'association-one-overlay'
                                : 'association-more-overlay',
                            location: '-30px',
                        }
                    },
                    {
                        type: 'Arrow',
                        options: {
                            location: 1,
                        }
                    },
                ]
            });
        } else {
            const connection = this.jsPlumbInstance.connect({
                source: parent,
                target: child,
                anchors: [[1, 0, 0, -1, -100, 0], [1, 0, 1, 0, 0, 50]],
                connector: {
                    type: 'Flowchart',
                    loopback: true
                },
                overlays: [
                    {
                        type: 'Label',
                        options: {
                            label: '*',
                            cssClass: 'association-more-overlay',
                            location: '15px',
                        }
                    },
                    {
                        type: 'Label',
                        options: {
                            label: associationName,
                            cssClass: 'association-name-overlay',
                            location: 0.5,
                            events: {
                                click: e => this.handleAssociationNameClick(e, connection, associationId)
                            }
                        }
                    },
                    {
                        type: 'Label',
                        options: {
                            label: '1',
                            cssClass: 'association-one-overlay',
                            location: '-15px',
                        }
                    }
                ]
            });
        }
    }

    refresh() {
        this.jsPlumbInstance.deleteEveryConnection();
        const entityNodes = this.viewportEle.querySelectorAll('[data-entity-node]');
        for (const entityNode of entityNodes) {
            this.jsPlumbInstance.removeAllEndpoints(entityNode);
            if (entityNode.attributes['data-class-name']) {
                const classNames = entityNode.attributes['data-class-name'].value.split(' ');
                for (const className of classNames) {
                    entityNode.classList.add(className);
                }
            }
            if (entityNode.attributes['data-location']) {
                const location = entityNode.attributes['data-location'].value.split(',');
                if (location.length === 2) {
                    entityNode.style.left = `${parseInt(location[0]) ?? 0}px`;;
                    entityNode.style.top = `${parseInt(location[1]) ?? 0}px`;
                }
            }
        }
        for (const entityNode of entityNodes) {
            this.jsPlumbInstance.addEndpoint(entityNode);
            if (entityNode.attributes['data-connections']) {
                const connections = entityNode.attributes['data-connections'].value.split(';');
                for (let connection of connections) {
                    const association = connection.split(',');
                    if (association.length === 6) {
                        const parent = this.findNode(association[2], entityNodes);
                        const child = this.findNode(association[4], entityNodes);
                        if (parent && child) {
                            this.connectNode(parent, child, association);
                        }
                    }
                }
            }
        }
    }

    dispose() {
        document.body.removeEventListener('mouseup', this.handleBodyMouseUp);
        this.viewportEle.removeEventListener('mouseup', this.handleEditorMouseUp);
        this.commandLineEle.removeEventListener('keypress', this.handleCommandLineInput);
        this.navigator.dispose();
        this.jsPlumbInstance.destroy();
    }
}

export function init(viewportEleId, commandLineEleId, dotNetHelper) {
    const viewportEle = document.getElementById(viewportEleId);
    if (viewportEle) {
        const commandLineEle = document.getElementById(commandLineEleId);
        viewportEle.associationEditor = new AssociationEditor(viewportEle, commandLineEle, dotNetHelper);
    }
};

export function refresh(viewportEleId) {
    const viewportEle = document.getElementById(viewportEleId);
    if (viewportEle?.associationEditor) {
        viewportEle.associationEditor.refresh();
    }
}

export function dispose(viewportEleId) {
    const viewportEle = document.getElementById(viewportEleId);
    if (viewportEle?.associationEditor) {
        viewportEle.associationEditor.dispose();
    }
}